13 标准内建对象
> Last Format Time:8/11/2026 21:45:40
本笔记由两个 JavaScript 教程目录中的同主题内容合并而成;全部旧知识保留在“旧笔记知识全集(按来源保留)”中,并由导航逐项索引。
当前前端开发关键要点
> [!important] 学习优先级:P1 > - 日期、正则和 JSON 都有明确边界:时区、正则灾难性回溯、不可序列化值需要特别留意。 > - 避免显式创建原始类型包装对象;使用对应静态方法或字面量。 > - Proxy 与 Reflect 适合元编程、响应式和校验封装,但会增加调试与性能成本。 > - 优先使用平台标准 API,并在兼容性要求明确后再决定是否引入 polyfill。
知识点导航
[现代JS教程与阮一峰ES6 · 5特殊内建对象/Date](./13 标准内建对象.md)
- Date
[现代JS教程与阮一峰ES6 · 5特殊内建对象/JSON](./13 标准内建对象.md)
- JSON
[现代JS教程与阮一峰ES6 · 5特殊内建对象/Proxy与Reflect](./13 标准内建对象.md)
- Reflect
[廖雪峰教程 · 5标准对象/5.1 简述](./13 标准内建对象.md)
- 包装对象
[廖雪峰教程 · 5标准对象/5.2 RegExp](./13 标准内建对象.md)
- 进阶
- RegExp
- 切分字符串
- 分组
- 贪婪匹配
- 全局搜索
现代前端补充与纠错
> [!info] 修改标记 > - 【修正】:旧教程中错误、过时或容易误导的内容。 > - 【补充】:旧教程未覆盖、但当前前端开发需要掌握的内容。 > - 【修正代码】:替换或校正了旧代码示例。
> [!warning] 下方保留教程原文;转换、国际化和日期处理的当前建议以本节为准。
数值与日期
- 【修正】
Number(value)与parseInt/parseFloat不等价:前者要求整体转换,后两者可读取合法前缀。 - 【补充】
Date同时混合时间点、时区展示与易错解析;传输使用明确格式,业务层明确时区,不要依赖含糊的本地日期字符串。 - 【补充】 金额和高精度十进制不要直接依赖二进制浮点;
BigInt也不表示小数。
Intl
- 【补充】 数字/货币:
Intl.NumberFormat;日期时间:Intl.DateTimeFormat;排序比较:Intl.Collator。 - 【补充】 相对时间、列表、复数规则、分词可分别使用
Intl.RelativeTimeFormat、ListFormat、PluralRules、Segmenter。 - 【补充】 格式化器可复用,不要在大型循环里为每个值重复构造。
正则与序列化
- 【补充】 动态拼入正则的用户文本必须转义;复杂文本解析不要强行用一个巨型正则完成。
- 【补充】
JSON.stringify不支持循环引用和BigInt,并会忽略/改变部分值;序列化协议应有明确 schema 与版本。
旧笔记知识全集(按来源保留)
现代JS教程与阮一峰ES6 · 5特殊内建对象/Date
Date
调用 new Date() 来创建一个新的 Date 对象。在调用时可以带有一些参数,如下所示: 不带参数 —— 创建一个表示当前日期和时间的 Date 对象
new Date(milliseconds) 创建一个 Date 对象,其时间等于 1970 年 1 月 1 日 UTC+0 之后经过的毫秒数milliseconds(1/1000 秒)。
new Date(datestring) 如果只有一个参数,并且是字符串,那么它会被自动解析。该算法与 Date.parse 所使用的算法相同
new Date(year, month, date, hours, minutes, seconds, ms) 使用当前时区中的给定组件创建日期。只有前两个参数是必须的。
year应该是四位数。为了兼容性,也接受 2 位数,并将其视为19xx,例如98与1998相同,但强烈建议始终使用 4 位数。month计数从0(一月)开始,到11(十二月)结束。date是当月的具体某一天,如果缺失,则为默认值1。- 如果
hours/minutes/seconds/ms缺失,则均为默认值0。
从 Date 对象中访问年、月等信息有多种方式: getFullYear() 获取年份(4 位数)很多 JavaScript 引擎都实现了一个非标准化的方法 getYear()。不推荐使用这个方法。它有时候可能会返回 2 位的年份信息。永远不要使用它。要获取年份就使用 getFullYear()。 getMonth() 获取月份,从 0 到 11。 getDate() 获取当月的具体日期,从 1 到 31,这个方法名称可能看起来有些令人疑惑。 getHours(),getMinutes(),getSeconds(),getMilliseconds() 获取相应的时间组件。
另外,我们还可以获取一周中的第几天: getDay() 获取一周中的第几天,从 0(星期日)到 6(星期六)。第一天始终是星期日,在某些国家可能不是这样的习惯,但是这不能被改变。
getTime() 返回日期的时间戳 —— 从 1970-1-1 00:00:00 UTC+0 开始到现在所经过的毫秒数。
getTimezoneOffset() 返回 UTC 与本地时区之间的时差,以分钟为单位
下列方法可以设置日期/时间组件:
setFullYear(year, [month], [date])setMonth(month, [date])setDate(date)setHours(hour, [min], [sec], [ms])setMinutes(min, [sec], [ms])setSeconds(sec, [ms])setMilliseconds(ms)setTime(milliseconds)(使用自 1970-01-01 00:00:00 UTC+0 以来的毫秒数来设置整个日期) 以上方法除了setTime()都有 UTC 变体,例如:setUTCHours()。
自动校准 是 Date 对象的一个非常方便的特性。 假设我们要在日期 “28 Feb 2016” 上加 2 天。结果可能是 “2 Mar” 或 “1 Mar”,因为存在闰年。但是我们不需要考虑这些,只需要直接加 2 天,剩下的 Date 对象会帮我们处理
let date = new Date();
date.setDate(date.getDate() + 100);
console.log(date);
//2025-10-02T14:40:25.361Z
当 Date 对象被转化为数字时,得到的是对应的时间戳,与使用 date.getTime() 的结果相同 日期可以相减,相减的结果是以毫秒为单位时间差。
let start = new Date(); // 开始测量时间
// do the job
for (let i = 0; i < 100000000; i++) {
let doSomething = i * i * i;
}
let end = new Date(); // 结束测量时间
console.log(`The loop took ${end - start} ms`);
//The loop took 41 ms
如果我们仅仅想要测量时间间隔,我们不需要 Date 对象
let start = Date.now(); // 开始测量时间
// do the job
for (let i = 0; i < 100000000; i++) {
let doSomething = i * i * i;
}
let end = Date.now(); // 结束测量时间
console.log(`The loop took ${end - start} ms`);
//The loop took 43 ms
它相当于 new Date().getTime(),但它不会创建中间的 Date 对象。因此它更快,而且不会对垃圾回收造成额外的压力。 这种方法很多时候因为方便,又或是因性能方面的考虑而被采用,例如使用 JavaScript 编写游戏或其他的特殊应用场景。
Date.prese(): 字符串的格式应该为:YYYY-MM-DDTHH:mm:ss.sssZ,其中:
YYYY-MM-DD—— 日期:年-月-日。- 字符
"T"是一个分隔符。 HH:mm:ss.sss—— 时间:小时,分钟,秒,毫秒。- 可选字符
'Z'为+-hh:mm格式的时区。单个字符Z代表 UTC+0 时区。
简短形式也是可以的,比如 YYYY-MM-DD 或 YYYY-MM,甚至可以是 YYYY。
Date.parse(str) 调用会解析给定格式的字符串,并返回时间戳(自 1970-01-01 00:00:00 起所经过的毫秒数)。如果给定字符串的格式不正确,则返回 NaN。
现代JS教程与阮一峰ES6 · 5特殊内建对象/JSON
JSON
Javascript Object Notation
JavaScript 提供了如下方法:
JSON.stringify将对象转换为 JSON。JSON.parse将 JSON 转换回对象。
JSON 编码的对象与对象字面量有几个重要的区别:
- ==字符串使用双引号==。JSON 中没有单引号或反引号。所以
'John'被转换为"John"。 - 对象==属性名称==也是==双引号==的。这是强制性的。所以
age:30被转换成"age":30。
JSON.stringify 也可以应用于原始(primitive)数据类型。 JSON 支持以下数据类型:
- Objects
{ ... } - Arrays
[ ... ] - Primitives:
- strings,
- numbers,
- boolean values
true/false, null。
JSON 是语言无关的纯数据规范,因此一些特定于 JavaScript 的对象属性会被 JSON.stringify 跳过。即:
- 函数属性(方法)。
- Symbol 类型的键和值。
- 存储
undefined的属性。
重要的限制:==不得有循环引用==。 支持嵌套对象转换,并且可以自动对其进行转换。
let obj = {
name: 'dano',
age: 21,
foods: {
apple: 23,
peal: 34,
shit: {
wee: 21,
ui:8,
}
}
}
let json = JSON.stringify(obj);
console.log(json);
//{"name":"dano","age":21,"foods":{"apple":23,"peal":34,"shit":{"wee":21,"ui":8}}}
let json2 = JSON.stringify(obj, ( key,value) => {
return ((key == 'name')) ? undefined : value;
})
console.log(json2);
//{"age":21,"foods":{"apple":23,"peal":34,"shit":{"wee":21,"ui":8}}}
JSON.stringify有三个参数:
- value 要编码的对象。
- replacer 要编码的属性数组或映射函数
function(key, value)。 - space 用于格式化的空格数量。
{
"age": 21,
"foods": {
"apple": 23,
"peal": 34,
"shit": {
"wee": 21,
"ui": 8
}
}
}
使用自定义的toJSON: 像 toString 进行字符串转换,对象也可以提供 toJSON 方法来进行 JSON 转换。如果可用,JSON.stringify 会自动调用它。
let obj = {
name: 'dano',
age: 21,
foods: {
apple: 23,
peal: 34,
shit: {
wee: 21,
ui:8,
}
},
toJSON() {
return this.name; //"dano"
}
}
使用JSON.prase来将JSON转换为对象:
let object = JSON.parse(json);
console.log(object);
{
name: 'dano',
age: 21,
foods: { apple: 23, peal: 34, shit: { wee: 21, ui: 8 } }
}
此函数有两个参数: str 要解析的 JSON 字符串。
reviver 可选的函数 function(key,value),该函数将为每个 (key, value) 对调用,并可以对值进行转换。
从服务器上获取的JSON的data的值是字符串,使用reviver将其转换为Data。其对嵌套的对象也起作用
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';
let meetup = JSON.parse(str, function (key, value) {
if (key == 'date') return new Date(value);
return value;
});
console.log(meetup.date.getDate());//30
现代JS教程与阮一峰ES6 · 5特殊内建对象/Proxy与Reflect
proxy代理器,用于拦截某些操作,修改原生的操作:
let proxy = new Proxy(target, handler)
target—— 是要包装的对象,可以是任何东西,包括函数。handler—— 代理配置:带有“捕捉器”(“traps”,即拦截操作的方法)的对象。比如get捕捉器用于读取target的属性,set捕捉器用于写入target的属性,等等。
对 proxy 进行操作,如果在 handler 中存在相应的捕捉器,则handler将运行,否则将直接对 target 进行处理:
let target = {};
let proxy = new Proxy(target, {}); // 空的 handler 对象
proxy.test = 5; // 写入 proxy 对象 (1)
alert(target.test); // 5,test 属性出现在了 target 中!
alert(proxy.test); // 5,我们也可以从 proxy 对象读取它 (2)
for(let key in proxy) alert(key); // test,迭代也正常工作 (3)
 带有get的代理器:
let dictionary = {
'Hello': 'Hola',
'Bye': 'Adiós'
};
dictionary = new Proxy(dictionary, {
get(target, phrase) { // 拦截读取属性操作
if (phrase in target) { //如果词典中有该短语
return target[phrase]; // 返回其翻译
} else {
// 否则返回未翻译的短语
return phrase;
}
}
});
// 在词典中查找任意短语!
// 最坏的情况也只是它们没有被翻译。
alert( dictionary['Hello'] ); // Hola
alert( dictionary['Welcome to Proxy']); // Welcome to Proxy(没有被翻译)
set:
let numbers = [];
numbers = new Proxy(numbers, { // (*)
set(target, prop, val) { // 拦截写入属性操作
if (typeof val == 'number') {
target[prop] = val;
return true;
} else {
return false;
}
}
});
numbers.push(1); // 添加成功
numbers.push(2); // 添加成功
alert("Length is: " + numbers.length); // 2
numbers.push("test"); // TypeError(proxy 的 'set' 返回 false)
alert("This line is never reached (error in the line above)");
没看完
Reflect
Reflect 是一个内建对象,可简化 Proxy 的创建。 前面所讲过的内部方法,例如 Get 和 Set 等,都只是规范性的,不能直接调用。 Reflect 对象使调用这些内部方法成为了可能。它的方法是内部方法的最小包装。
廖雪峰教程 · 5标准对象/5.1 简述
在JavaScript的世界里,一切都是对象。
但是某些对象还是和其他对象不太一样。为了区分对象的类型,我们用typeof操作符获取对象的类型,它总是返回一个字符串:
typeof 123; // 'number'
typeof 123n; // 'bigint'
typeof NaN; // 'number'
typeof 'str'; // 'string'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof Math.abs; // 'function'
typeof null; // 'object'
typeof []; // 'object'
typeof {}; // 'object'
可见,number、bigint、string、boolean、function和undefined有别于其他类型。特别注意null的类型是object,Array的类型也是object,如果我们用typeof将无法区分出null、Array和通常意义上的object——{}。
包装对象
除了这些类型外,JavaScript还提供了包装对象,熟悉Java的小伙伴肯定很清楚int和Integer这种暧昧关系。
number、boolean和string都有包装对象。没错,在JavaScript中,字符串也区分string类型和它的包装类型。包装对象用new创建:
let n = new Number(123); // 123,生成了新的包装类型
let b = new Boolean(true); // true,生成了新的包装类型
let s = new String('str'); // 'str',生成了新的包装类型
虽然包装对象看上去和原来的值一模一样,显示出来也是一模一样,但他们的类型已经变为object了!所以,包装对象和原始值用===比较会返回false:
typeof new Number(123); // 'object'
new Number(123) === 123; // false
typeof new Boolean(true); // 'object'
new Boolean(true) === true; // false
typeof new String('str'); // 'object'
new String('str') === 'str'; // false
所以_闲的蛋疼也不要使用包装对象_!尤其是针对string类型!!!
如果我们在使用Number、Boolean和String时,没有写new会发生什么情况?
此时,Number()、Boolean和String()被当做普通函数,把任何类型的数据转换为number、boolean和string类型(注意不是其包装类型):
*8/11/26 【修正】原内容:`Number()` 相当于 `parseInt()` 或 `parseFloat()`;修改点:三者转换规则不同。*
let n = Number('123'); // 123;但 Number、parseInt、parseFloat 的解析语义不同
typeof n; // 'number'
let b = Boolean('true'); // true
typeof b; // 'boolean'
let b2 = Boolean('false'); // true! 'false'字符串转换结果为true!因为它是非空字符串!
let b3 = Boolean(''); // false
let s = String(123.45); // '123.45'
typeof s; // 'string'
是不是感觉头大了?这就是JavaScript特有的催眠魅力!
总结一下,有这么几条规则需要遵守:
- 不要使用
new Number()、new Boolean()、new String()创建包装对象; - 用
parseInt()或parseFloat()来转换任意类型到number; - 用
String()来转换任意类型到string,或者直接调用某个对象的toString()方法; - 通常不必把任意类型转换为
boolean再判断,因为可以直接写if (myVar) {...}; typeof操作符可以判断出number、boolean、string、function和undefined;- 判断
Array要使用Array.isArray(arr); - 判断
null请使用myVar === null; - 判断某个全局变量是否存在用
typeof window.myVar === 'undefined'; - 函数内部判断某个变量是否存在用
typeof myVar === 'undefined'。
最后有细心的同学指出,任何对象都有toString()方法吗?null和undefined就没有!确实如此,这两个特殊值要除外,虽然null还伪装成了object类型。
更细心的同学指出,number对象调用toString()报SyntaxError:
123.toString(); // SyntaxError
遇到这种情况,要特殊处理一下:
123..toString(); // '123', 注意是两个点!
(123).toString(); // '123'
不要问为什么,这就是JavaScript代码的乐趣!
廖雪峰教程 · 5标准对象/5.2 RegExp
正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。
所以我们判断一个字符串是否是合法的Email的方法是:
- 创建一个匹配Email的正则表达式;
- 用该正则表达式去匹配用户的输入来判断是否合法。
因为正则表达式也是用字符串表示的,所以,我们要首先了解如何用字符来描述字符。
在正则表达式中,如果直接给出字符,就是精确匹配。用\d可以匹配一个数字,\w可以匹配一个字母或数字,所以:
'00\d'可以匹配'007',但无法匹配'00A';'\d\d\d'可以匹配'010';'\w\w'可以匹配'js';
.可以匹配任意字符,所以:
'js.'可以匹配'jsp'、'jss'、'js!'等等。
要匹配变长的字符,在正则表达式中,用*表示任意个字符(包括0个),用+表示至少一个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符:
来看一个复杂的例子:\d{3}\s+\d{3,8}。
我们来从左到右解读一下:
\d{3}表示匹配3个数字,例如'010';\s可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,例如匹配' ','\t\t'等;\d{3,8}表示3-8个数字,例如'1234567'。
综合起来,上面的正则表达式可以匹配以任意个空格隔开的带区号的电话号码。
如果要匹配'010-12345'这样的号码呢?由于'-'是特殊字符,在正则表达式中,要用'\'转义,所以,上面的正则是\d{3}\-\d{3,8}。
但是,仍然无法匹配'010 - 12345',因为带有空格。所以我们需要更复杂的匹配方式。
进阶
要做更精确地匹配,可以用[]表示范围,比如:
[0-9a-zA-Z\_]可以匹配一个数字、字母或者下划线;[0-9a-zA-Z\_]+可以匹配至少由一个数字、字母或者下划线组成的字符串,比如'a100','0_Z','js2015'等等;[a-zA-Z\_\$][0-9a-zA-Z\_\$]*可以匹配由字母或下划线、开头,后接任意个由一个数字、字母或者下划线、开头,后接任意个由一个数字、字母或者下划线、组成的字符串,也就是JavaScript允许的变量名;[a-zA-Z\_\$][0-9a-zA-Z\_\$]{0, 19}更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。
A|B可以匹配A或B,所以(J|j)ava(S|s)cript可以匹配'JavaScript'、'Javascript'、'javaScript'或者'javascript'。
^表示行的开头,^\d表示必须以数字开头。
$表示行的结束,\d$表示必须以数字结束。
你可能注意到了,js也可以匹配'jsp',但是加上^js$就变成了整行匹配,就只能匹配'js'了。
RegExp
有了准备知识,我们就可以在JavaScript中使用正则表达式了。
JavaScript有两种方式创建一个正则表达式:
第一种方式是直接通过/正则表达式/写出来,第二种方式是通过new RegExp('正则表达式')创建一个RegExp对象。
两种写法是一样的:
let re1 = /ABC\-001/;
let re2 = new RegExp('ABC\\-001');
re1; // /ABC\-001/
re2; // /ABC\-001/
注意,如果使用第二种写法,因为字符串的转义问题,字符串的两个\\实际上是一个\。
先看看如何判断正则表达式是否匹配:
let re = /^\d{3}\-\d{3,8}$/;
re.test('010-12345'); // true
re.test('010-1234x'); // false
re.test('010 12345'); // false
RegExp对象的test()方法用于测试给定的字符串是否符合条件。
切分字符串
用正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:
'a b c'.split(' '); // ['a', 'b', '', '', 'c']
嗯,无法识别连续的空格,用正则表达式试试:
'a b c'.split(/\s+/); // ['a', 'b', 'c']
无论多少个空格都可以正常分割。加入,试试:
'a,b, c d'.split(/[\s\,]+/); // ['a', 'b', 'c', 'd']
再加入;试试:
'a,b;; c d'.split(/[\s\,\;]+/); // ['a', 'b', 'c', 'd']
如果用户输入了一组标签,下次记得用正则表达式来把不规范的输入转化成正确的数组。
分组
除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)。比如:
^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:
let re = /^(\d{3})-(\d{3,8})$/;
re.exec('010-12345'); // ['010-12345', '010', '12345']
re.exec('010 12345'); // null
如果正则表达式中定义了组,就可以在RegExp对象上用exec()方法提取出子串来。
exec()方法在匹配成功后,会返回一个Array,第一个元素是正则表达式匹配到的整个字符串,后面的字符串表示匹配成功的子串。
exec()方法在匹配失败时返回null。
提取子串非常有用。来看一个更凶残的例子:
let re = /^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$/;
re.exec('19:05:30'); // ['19:05:30', '19', '05', '30']
这个正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也无法做到完全验证,比如识别日期:
let re = /^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$/;
对于'2-30','4-31'这样的非法日期,用正则还是识别不了,或者说写出来非常困难,这时就需要程序配合识别了。
贪婪匹配
需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0:
let re = /^(\d+)(0*)$/;
re.exec('102300'); // ['102300', '102300', '']
由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0*只能匹配空字符串了。
必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配:
let re = /^(\d+?)(0*)$/;
re.exec('102300'); // ['102300', '1023', '00']
全局搜索
JavaScript的正则表达式还有几个特殊的标志,最常用的是g,表示全局匹配:
let r1 = /test/g;
// 等价于:
let r2 = new RegExp('test', 'g');
全局匹配可以多次执行exec()方法来搜索一个匹配的字符串。当我们指定g标志后,每次运行exec(),正则表达式本身会更新lastIndex属性,表示上次匹配到的最后索引:
let s = 'JavaScript, VBScript, JScript and ECMAScript';
let re=/[a-zA-Z]+Script/g;
// 使用全局匹配:
re.exec(s); // ['JavaScript']
re.lastIndex; // 10
re.exec(s); // ['VBScript']
re.lastIndex; // 20
re.exec(s); // ['JScript']
re.lastIndex; // 29
re.exec(s); // ['ECMAScript']
re.lastIndex; // 44
re.exec(s); // null,直到结束仍没有匹配到
全局匹配类似搜索,因此不能使用/^...$/,那样只会最多匹配一次。
正则表达式还可以指定i标志,表示忽略大小写,m标志,表示执行多行匹配。